package com.lsq.baseadmin.common.exception;

import com.google.gson.*;
import com.lsq.baseadmin.common.component.SendMail;
import com.lsq.baseadmin.common.component.SpringContextUtils;
import com.lsq.baseadmin.common.constant.ConstantSystem;
import com.lsq.baseadmin.common.utils.*;
import com.lsq.baseadmin.modules.maintenance.entity.Log;
import com.lsq.baseadmin.modules.maintenance.service.LogService;
import com.lsq.baseadmin.modules.manage.utils.AuthUtil;
import com.lsq.baseadmin.modules.manage.vo.UserVo;
import com.lsq.baseadmin.modules.setting.entity.SystemSetting;
import com.lsq.baseadmin.modules.setting.service.SystemSettingService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author pc12138 2020/10/23 10:44
 * @since 全局异常
 * 参数不正确等请求服务端无法理解 = BAD_REQUEST
 * 先决条件（请求必须要满足条件）、不满足则 = PRECONDITION_FAILED
 * 没有权限 = FORBIDDEN
 * 没有登录 = UNAUTHORIZED
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    private final LogService logService;
    private SystemSettingService systemSettingService;
    private SendMail sendMail;

    public GlobalExceptionHandler(LogService logService, SystemSettingService systemSettingService, SendMail sendMail) {
        this.logService = logService;
        this.systemSettingService = systemSettingService;
        this.sendMail = sendMail;
    }

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(value = MyException.class)
    public ResponseEntity<ApiError> badRequestException(MyException e) {
        // 打印堆栈信息
        log.info(e.getMessage());
        return buildResponseEntity(ApiError.error(e.getStatus(), e.getMessage()));
    }

    /**
     *  缺少参数异常
     *  getParameterName() 缺少的参数名称
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseEntity<ApiError> requestMissingServletRequest(MissingServletRequestParameterException e){
        log.info(ThrowableUtil.getStackTrace(e));
        return buildResponseEntity(ApiError.error(HttpStatus.BAD_REQUEST.value(), "缺少参数或参数不正确,参数名称为" + e.getParameterName()));
    }
    /**
     *  参数转换异常
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<ApiError> httpMessageNotReadableException(HttpMessageNotReadableException e){
        log.info(ThrowableUtil.getStackTrace(e));
        return buildResponseEntity(ApiError.error(HttpStatus.BAD_REQUEST.value(), "缺少参数或参数不正确!"));
    }

    /**
     * 处理所有接口数据验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
        // 打印堆栈信息
        log.info(ThrowableUtil.getStackTrace(e));
        String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
        String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        String msg = "不能为空";
        if(msg.equals(message)){
            message = str[1] + ":" + message;
        }
        return buildResponseEntity(ApiError.error(message));
    }

    /**
     * 处理所有不可知的异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiError> handleException(Exception e){
        String stackTrace = ThrowableUtil.getStackTrace(e);
        // 打印堆栈信息
        log.error(stackTrace);
        ThreadPoolUtil.threadPool.execute(() -> {
            sendEmail(stackTrace);
            saveLog(e);
        });
        return buildResponseEntity(ApiError.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "系统异常，请联系管理员！"));
    }

    /**
     * 统一返回
     */
    private ResponseEntity<ApiError> buildResponseEntity(ApiError apiError) {
        return new ResponseEntity<>(apiError, HttpStatus.valueOf(apiError.getStatus()));
    }

    /**
     * 发送错误信息到邮件
     * @param error 错误信息
     */
    private void sendEmail(String error) {
        // 不是生成环境发送
        if(!SpringContextUtils.getActiveProfile().equals(ConstantSystem.ACTIVE_PROFILE_PRO)) {
            return;
        }

        // 发送异常到邮箱
        List<SystemSetting> list = systemSettingService.list();
        if(list.isEmpty()) {
            log.error("没有配置邮箱!");
            return;
        }
        SystemSetting systemSetting = list.get(0);
        String email = systemSetting.getEmail();
        if(!StringUtils.hasText(email)) {
            log.error("没有配置邮箱!");
            return;
        }
        Gson gson = new Gson();
        JsonArray jsonArray = JsonParser.parseString(email).getAsJsonArray();
        for (JsonElement element : jsonArray) {
            JsonObject jsonObject = gson.fromJson(element, JsonObject.class);
            ThreadPoolUtil.threadPool.execute( () -> sendMail.sendSimpleMail(jsonObject.get("url").getAsString(), "baseAdmin系统异常", error));
        }
    }

    private void saveLog(Exception e) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        Log log = new Log();
        log.setType("ERROR");
        String ip = request.getRemoteAddr();
        log.setAddress(IpUtil.getCityInfo(ip));
        log.setBrowser(MyUtil.getBrowser(request));
        log.setRequestIp(ip);
        log.setMethod(request.getRequestURI());
        // 请求参数
        StringBuilder params = new StringBuilder("{");
        Map<String, String[]> parameterMap = request.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            params.append(entry.getKey()).append("：").append(Arrays.toString(entry.getValue()));
        }
        log.setParams(params.toString() + " }");
        // 请求人
        UserVo loginUser = AuthUtil.getLoginUser(request.getSession());
        if (null != loginUser) {
            log.setUsername(loginUser.getUser().getLoginName());
        }
        log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());

        logService.save(log);
    }

}
